package simpledb.optimizer;

import simpledb.execution.Predicate;

import java.util.Arrays;

/** A class to represent a fixed-width histogram over a single integer-based field.
 */
public class IntHistogram {

    private final int[] buckets;
    private final int min;
    private final int max;
    private final double width;
    private int count;

    /**
     * Create a new IntHistogram.
     * 
     * This IntHistogram should maintain a histogram of integer values that it receives.
     * It should split the histogram into "buckets" buckets.
     * 
     * The values that are being histogrammed will be provided one-at-a-time through the "addValue()" function.
     * 
     * Your implementation should use space and have execution time that are both
     * constant with respect to the number of values being histogrammed.  For example, you shouldn't 
     * simply store every value that you see in a sorted list.
     * 
     * @param buckets The number of buckets to split the input value into.
     * @param min The minimum integer value that will ever be passed to this class for histogramming
     * @param max The maximum integer value that will ever be passed to this class for histogramming
     */
    public IntHistogram(int buckets, int min, int max) {
    	// some code goes here
        this.buckets = new int[buckets];
        this.min = min;
        this.max = max;
        // 处理max == min的特殊情况（避免除零）
        if (max == min) {
            this.width = 1.0;  // 任意值，因为所有值都在同一个桶
        } else {
            // 注意，当前桶的范围是[a,b)
            // 区间宽度是[min, max]，需要加上1.0
            // 保证区间宽度最小为1.0，防止除了之后，选择率反而变大了。这是一种方法，也可以让所有字段的所有表的桶宽
            // 都一致，这样放大的倍数是一样的，所以没有影响。但是其实就算放大到1，因为桶宽不同，所以大家会有不同的缩放？
            this.width = Math.max(1.0, (max - min + 1.0)/ buckets);
        }
        this.count = 0;
    }

    /**
     * Add a value to the set of values that you are keeping a histogram of.
     * @param v Value to add to the histogram
     */
    public void addValue(int v) {
    	// some code goes here
        if (v < min || v > max) {
            return;
        }
        buckets[(int) Math.floor((v-min)/width)]++;
        count++;
    }

    /**
     * Estimate the selectivity of a particular predicate and operand on this table.
     * 
     * For example, if "op" is "GREATER_THAN" and "v" is 5, 
     * return your estimate of the fraction of elements that are greater than 5.
     * 
     * @param op Operator
     * @param v Value
     * @return Predicted selectivity of this particular operator and value
     */
    public double estimateSelectivity(Predicate.Op op, int v) {
        if (count == 0) {
            return 0.0;
        }
        // System.out.println(Arrays.toString(buckets));
    	// some code goes here
        switch (op) {
            case LESS_THAN -> {
                if (v<=min) {
                    return 0.0;
                }
                if (v>max) {
                    return 1.0;
                }
                int index = (int) Math.floor((v-min)/width);
                double sum = buckets[index] * (v-min-index*width)/width;
                for (int i=0; i<index; i++) {
                    sum += buckets[i];
                }
                return sum/count;
            }
            case EQUALS -> {
                // if (v < min || v > max) {
                //     return 0.0;
                // }
                // int index1 = (int) Math.floor((v - min) / width);
                // int index2 = (int) Math.floor((v+1 - min) / width);
                // System.out.println(index1);
                // System.out.println(index2);
                // // 打印关键信息验证
                // double a = min + index * width;
                // double b = a + width;
                // System.out.println("v=" + v + "，所在桶[" + a + ", " + b + ")，index=" + index);
                // return (buckets[index] / width) / count;
                // 注意当前的euqals都应该是代表一个[v, v+1)的区间，如果直接按注释的代码来，可能区间其实是分在了两个桶内，值算的就不对了,一个桶宽度可能很小的
                return estimateSelectivity(Predicate.Op.LESS_THAN, v+1) - estimateSelectivity(Predicate.Op.LESS_THAN, v);
            }
            case LESS_THAN_OR_EQ -> {
                return estimateSelectivity(Predicate.Op.LESS_THAN, v+1);
            }
            case GREATER_THAN -> {
                return 1.0 - estimateSelectivity(Predicate.Op.LESS_THAN_OR_EQ, v);
            }
            case GREATER_THAN_OR_EQ -> {
                return 1.0 - estimateSelectivity(Predicate.Op.LESS_THAN, v);
            }
            case NOT_EQUALS -> {
                return 1.0 - estimateSelectivity(Predicate.Op.EQUALS, v);
            }
            default -> {
                throw new IllegalArgumentException("Operation is illegal");
            }
        }
    }
    
    /**
     * @return
     *     the average selectivity of this histogram.
     *     
     *     This is not an indispensable method to implement the basic
     *     join optimization. It may be needed if you want to
     *     implement a more efficient optimization
     * */
    public double avgSelectivity()
    {
        // some code goes here
        if (count == 0) {
            return 0.0;
        }
        double sum = 0.0;
        for (int bucket : buckets) {
            // 桶内所有元素的选择性之和：单个元素的选择性为(bucket/width)/tuple，桶内所有再乘以width
            sum += bucket*1.0/count;
        }
        // 所有元素的平均（max-min+1）= buckets.length
        return (sum / buckets.length);
    }
    
    /**
     * @return A string describing this histogram, for debugging purposes
     */
    @Override
    public String toString() {
        return "IntHistogram{" +
                "buckets=" + Arrays.toString(buckets) +
                ", min=" + min +
                ", max=" + max +
                ", width=" + width +
                ", count=" + count +
                '}';
    }
}
